package com.myrice.remote.deploy;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import com.myrice.client.DeployException;
import com.myrice.core.impl.DefaultContext;
import com.myrice.remote.ServiceObject;
import com.myrice.remote.impl.DefaultRemoteMethodCollection;
import com.myrice.remote.impl.DefaultServiceObject;
import com.myrice.util.ThreadContext;
import com.myrice.utils.ConfigUtils;

public class FileSystemDeployContext extends DefaultContext implements
		DeployContext {
	private static final Logger log = Logger
			.getLogger(FileSystemDeployContext.class);

	private static final long DEPLOY_SPARE_TIME = 5000;

	private long lastDeployTime;
	private File deployFile;
	private File pathFile;

	private Properties props;

	private ClassLoader classLoader;

	private IConfigHandler redeployUndeployHandler = new IConfigHandler() {

		public void handle(String name, String className, String handle,
				String interfaces, Properties props0) throws Exception {
			// log.debug("redeploy scan:" + name);

			if (props.containsKey(name) == false) {
				log.info("redeploy removed: " + name + "  handle:" + handle
						+ "  interfaces:" + interfaces);
				// 已删除的，取消部署
				if (status == STATUS_DEPLOY) {
					ServiceObject so = (ServiceObject) removeAttribute(DefaultRemoteMethodCollection
							.mappingInvokeHandle(handle));
					if (so != null) {
						handleUndeployInstance(so);
						so.destroy();
					}
				}
			} else if (props.containsKey(name + ".handle")) {
				String handle0 = props.getProperty(name + ".handle").trim();
				// 更换handle，取消部署
				if (!handle0.equals(handle)) {
					log.info("redeploy change-handle: " + name + "  handle:"
							+ handle + " -> " + handle0);
					if (status == STATUS_DEPLOY) {
						ServiceObject so = (ServiceObject) removeAttribute(DefaultRemoteMethodCollection
								.mappingInvokeHandle(handle));
						if (so != null) {
							handleUndeployInstance(so);
							so.destroy();
						}
					}
				}
			} else {
				log.warn("Not found handle: " + name);
			}
		}
	};

	private byte status;

	public FileSystemDeployContext(File path) {
		this.pathFile = path;
		this.deployFile = new File(path,
				FileSystemDeployContextHandler.DEPLOY_CONFIG_FILE_PATH);
		loadConfig();
	}

	private void loadConfig() {
		lastDeployTime = deployFile.lastModified();
		props = new Properties();
		InputStream in = null;
		try {
			in = new FileInputStream(deployFile);
			props.load(in);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (in != null)
				try {
					in.close();
				} catch (IOException e) {
				}
		}
	}

	public void onLoad(DeployHandler contextHandler) {
		if (Boolean.parseBoolean(props.getProperty("auto.load", "true"))) {
			doLoad(contextHandler);
		}
	}

	public void doLoad(final DeployHandler handler) {
		// if (status >= STATUS_LOAD)
		// throw new DeployException("Have been loaded: " + path);
		loadConfig();

		log.info("Deploy onload:  " + pathFile + "  deploy-date:"
				+ new Date(lastDeployTime));

		// try {
		// if (classLoader != null)
		// classLoader.close();
		// } catch (Throwable e) {
		// }

		try {
			URL[] urls = getClassPathUrls();
			if (classLoader != null) {
				destroyClassLoader();
			}
			classLoader = newClassLoader(urls);
			log.debug("classpath: " + Arrays.toString(urls));
		} catch (Throwable e) {
			throw new DeployException(
					"Create the deployment of the class loader failure.  deploy:"
							+ pathFile, e);
		}

		// 实例化
		Thread thread = Thread.currentThread();
		ClassLoader loader = thread.getContextClassLoader();

		boolean inited = ThreadContext.contains();
		Object app = null;
		try {
			thread.setContextClassLoader(classLoader);
			if (!inited) {
				ThreadContext.init();
			}
			app = ThreadContext.setAttribute(ThreadContext.SCOPE_APP,
					handler.getServerHandler());

			handleDeployConfigAll(new IConfigHandler() {

				public void handle(String name, String className,
						String handle, String interfaces, Properties props)
						throws Exception {
					Object instance = handler.createObject(className,
							FileSystemDeployContext.this);
					Class<?>[] proxyInterfaces = ConfigUtils
							.parseInterfaces(interfaces);

					DefaultServiceObject so = new DefaultServiceObject(handle,
							instance, proxyInterfaces);

					so.init();

					log.info("Loaded instance: " + name + "  handle:" + handle
							+ "  interfaces:" + interfaces + "  (" + className
							+ ")");
					if (status == STATUS_DEPLOY) {
						handleDeployInstance(instance);
					}
					Object redeploy = setAttribute(so.getInvokeHandle(), so);
					if (redeploy != null && redeploy instanceof ServiceObject) {
						if (status == STATUS_DEPLOY) {
							ServiceObject so0 = (ServiceObject) redeploy;
							handleUndeployInstance(so0);
							so0.destroy();
						}
					}
				}
			});
			if (status < STATUS_LOAD)
				status = STATUS_LOAD;
		} finally {
			thread.setContextClassLoader(loader);
			if (!inited) {
				ThreadContext.destory();
			} else {
				ThreadContext.setAttribute(ThreadContext.SCOPE_APP, app);
			}
		}
	}

	private URL[] getClassPathUrls() throws IOException {
		File libDirFile = new File(pathFile, "lib/");
		if (libDirFile.exists() == false)
			throw new IllegalStateException(
					"Lib directory does not exist. dirname: " + libDirFile);

		File cacheFile = new File(pathFile, "_cache/");
		if (cacheFile.exists() == false)
			FileUtils.forceMkdir(cacheFile);

		File libDirCacheFile = new File(cacheFile, "lib/");
		if (libDirCacheFile.exists())
			FileUtils.deleteDirectory(libDirCacheFile);
		FileUtils.copyDirectory(libDirFile, libDirCacheFile);

		List<URL> urls = new ArrayList<URL>();

		File[] list = libDirCacheFile.listFiles(new FileFilter() {

			public boolean accept(File pathname) {
				return pathname.getName().endsWith(".jar");
			}
		});
		if (list != null) {
			for (int i = 0; i < list.length; i++) {
				try {
					urls.add(list[i].toURI().toURL());
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}
			}
		}

		File classesDirFile = new File(pathFile, "classes/");
		if (classesDirFile.exists()) {
			File classesDirFileCache = new File(cacheFile, "classes/");
			if (classesDirFileCache.exists())
				FileUtils.deleteDirectory(classesDirFileCache);
			FileUtils.copyDirectory(classesDirFile, classesDirFileCache);

			urls.add(classesDirFileCache.toURI().toURL());
		}
		handleURLs(urls);
		return urls.toArray(new URL[urls.size()]);
	}

	public static interface IConfigHandler {

		void handle(String name, String className, String handle,
				String interfaces, Properties props) throws Exception;

	}

	private void handleDeployConfigAll(IConfigHandler handler) {
		handleDeployConfigAll(props, handler);
	}

	private static void handleDeployConfigAll(Properties props,
			IConfigHandler handler) {
		Enumeration<?> enums = props.propertyNames();
		while (enums.hasMoreElements())
			try {
				String name = (String) enums.nextElement();
				if (name.indexOf(".") == -1) {
					String className = props.getProperty(name);
					String handle = props.getProperty(name + ".handle");
					String interfaces = props.getProperty(name + ".interfaces");
					if (handle != null && interfaces != null) {
						className = className.trim();
						handle = handle.trim();
						interfaces = interfaces.trim();
						handler.handle(name, className, handle, interfaces,
								props);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
	}

	public void onDeploy(DeployHandler handler) {
		if (Boolean.parseBoolean(props.getProperty("auto.deploy", "true"))) {
			doDeploy(handler);
		}
	}

	public void doDeploy(final DeployHandler handler) {
		if (status != STATUS_LOAD)
			throw new DeployException("Not loading or have been deployed.");
		log.info("Deploy: " + pathFile + "    start...");
		for (Object value : this.getAttributeValues())
			if (value != null && value instanceof ServiceObject) {
				ServiceObject serviceObject = (ServiceObject) value;
				Object instance = serviceObject.getInstance();
				handleDeployInstance(instance);
			}
		status = STATUS_DEPLOY;
		log.info("Deploy do deploy: " + pathFile + "    finished.");
	}

	private void handleDeployInstance(Object instance) {
		log.debug("handle deploy Instance: " + instance);
		if (instance instanceof Deployable)
			try {
				((Deployable) instance).onDeploy();
			} catch (Exception e) {
				e.printStackTrace();
			}
	}

	public void onMonitoring(DeployHandler handler, String host) {
		if (Boolean.parseBoolean(props.getProperty("auto.undeploy", "true"))
				&& deployFile.exists() == false) {
			if (status == STATUS_DEPLOY) {
				onUndeploy(handler);// 取消部署
			} else if (status == STATUS_LOAD) {
				onDestroy(handler, host);
			}
		} else if (status == STATUS_DEPLOY) {
			long lastTime = deployFile.lastModified();
			if (lastTime > lastDeployTime + DEPLOY_SPARE_TIME) {
				onRedeploy(handler);
			}
		}
	}

	public void onDestroy(DeployHandler contextHandler, String host) {

		doDestroy(contextHandler, host);

	}

	public void doDestroy(DeployHandler handler, String host) {
		if (status >= STATUS_DEPLOY) {
			throw new DeployException("Have been deployed online.");
		}
		status = STATUS_DESTORY;
		handler.removeDeployContext(host, getContextKey());// 注销DeployContext

		boolean inited = ThreadContext.contains();
		Object app = null;
		try {
			if (!inited) {
				ThreadContext.init();
			}
			app = ThreadContext.setAttribute(ThreadContext.SCOPE_APP,
					handler.getServerHandler());

			destroy();

		} finally {
			if (!inited) {
				ThreadContext.destory();
			} else {
				ThreadContext.setAttribute(ThreadContext.SCOPE_APP, app);
			}
		}
	}

	public String getContextKey() {
		return AbstractDeployContextHandler.getContextKey(pathFile.getName());
	}

	protected void onRedeploy(DeployHandler handler) {
		if (status >= STATUS_LOAD) {
			if (!Boolean.parseBoolean(props.getProperty("auto.reload", "true"))) {
				return;
			}
			log.info("Redeploy: " + pathFile + "  prev-deploy-date:"
					+ new Date(lastDeployTime) + "    start...");

			Properties oldprops = this.props;

			onLoad(handler);

			boolean inited = ThreadContext.contains();
			Object app = null;
			try {
				if (!inited) {
					ThreadContext.init();
				}
				app = ThreadContext.setAttribute(ThreadContext.SCOPE_APP,
						handler.getServerHandler());

				// 取消部署已不存在的对象
				handleDeployConfigAll(oldprops, redeployUndeployHandler);
			} finally {
				if (!inited) {
					ThreadContext.destory();
				} else {
					ThreadContext.setAttribute(ThreadContext.SCOPE_APP, app);
				}
			}
			// onDeploy(handler);

			log.info("Redeploy: " + pathFile + "  deploy-date:"
					+ new Date(lastDeployTime) + "   finished.");
		}

	}

	public void onUndeploy(DeployHandler contextHandler) {
		if (status < STATUS_DEPLOY)
			return;

		doUndeploy(contextHandler);

	}

	public void doUndeploy(DeployHandler contextHandler) {
		if (status < STATUS_DEPLOY)
			throw new DeployException("No deployment online.");

		log.info("Deploy  undeploy: " + pathFile + "    start...");

		status = STATUS_LOAD;

		for (Object value : getAttributeValues())
			if (value != null && value instanceof ServiceObject)
				try {
					String handle = ((ServiceObject) value).getHandle();
					doUndeploy(handle);
				} catch (Exception e) {
					e.printStackTrace();
				}

		log.info("Deploy  undeploy: " + pathFile + "    finished.");
	}

	protected void doUndeploy(String handle) {
		int ih = DefaultRemoteMethodCollection.mappingInvokeHandle(handle);
		Object obj = getAttribute(ih);
		if (obj != null && obj instanceof ServiceObject) {
			ServiceObject so = (ServiceObject) obj;

			log.info("undeploy instance,   handle: " + handle
					+ "  service-object:" + so);

			handleUndeployInstance(so);
		}
	}

	private void handleUndeployInstance(ServiceObject so) {
		Object instance = so.getInstance();
		log.debug("handle undeploy Instance: " + instance);
		if (instance instanceof Deployable) {
			((Deployable) instance).onUndeploy();
		}
	}

	public void destroy() {
		log.info("Deploy destroy:  " + pathFile);

		for (Object value : this.getAttributeValues())
			if (value instanceof ServiceObject)
				try {
					((ServiceObject) value).destroy();
				} catch (Exception e) {
					e.printStackTrace();
				}

		this.clear();

		// try {
		// if (this.classLoader != null)
		// this.classLoader.close();
		// } catch (Throwable e) {
		// }
		if (props != null)
			this.props.clear();

		destroyClassLoader();

		this.props = null;
		this.deployFile = null;
		this.pathFile = null;
	}

	protected void handleURLs(List<URL> urls) {
		String classpath = props.getProperty("classpath");
		if (classpath != null) {
			String[] urlsStr = classpath.split(";");
			for (String urlStr : urlsStr) {
				if (urlStr.indexOf("://") != -1) {
					try {
						urls.add(new URL(urlStr));
					} catch (MalformedURLException e) {
						e.printStackTrace();
					}
				} else {
					try {
						urls.add(new File(urlStr).toURI().toURL());
					} catch (MalformedURLException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	protected ClassLoader newClassLoader(URL[] urls) {
		Class<?> clazz = null;
		String classStr = props.getProperty("class-loader");
		if (classStr != null) {
			try {
				log.info("Custm class-loader: " + classStr + "  deploy: "
						+ getName());
				clazz = Class.forName(classStr);
				if (clazz.isAssignableFrom(DeployClassLoader.class)) {
					Constructor<?> cons = clazz.getConstructor(URL[].class);
					DeployClassLoader dcl = (DeployClassLoader) cons
							.newInstance(new Object[] { urls });
					dcl.init(this);
					return dcl.getClassLoader();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return new URLClassLoader(urls);
	}

	public String getName() {
		return pathFile.getName();
	}

	protected void destroyClassLoader() {
		if (classLoader instanceof DeployClassLoader) {
			((DeployClassLoader) classLoader).destroy();
		}
		this.classLoader = null;
	}

	public String getPath() {
		return pathFile.getPath();
	}

	public Properties getProperties() {
		return props;
	}

	public boolean isDeployed() {
		return status == STATUS_DEPLOY;
	}

	public byte getStatus() {
		return status;
	}

	public ClassLoader getClassLoader() {
		return classLoader;
	}
}
